package com.bmft.project02.string.impl;

import com.bmft.project02.node.Node;
import com.bmft.project02.string.LinkedStringInterface;

import java.lang.reflect.GenericDeclaration;
import java.util.Arrays;

/**
 * @author Jarvan
 * @version 1.0
 * @create 2020/9/24 12:55
 */
public class LinkedString<E> implements LinkedStringInterface<E> {
    /**
     * First Node.
     */
    private Node<E> first;
    /**
     * Last Node.
     */
    private Node<E> last;
    /**
     * Size of the String.
     */
    private int size;

    /**
     * Return the char value of the index.
     *
     * @param index index.
     * @return Return the char value of the index.
     */
    @Override
    public E charAt(int index) {
        if (index > size) {
            System.err.println("you index value out of range of String");
            throw new IndexOutOfBoundsException();
        } else if (index == size) {
            return last.getItem();
        }
        Node<E> node = first;
        for (int i = 0; i < size; i++) {
            if (i == index) {
                return node.getItem();
            } else {
                node = node.getNext();
            }
        }
        return null;
    }

    /**
     * Concat tow LinkedString to a new LinkedString not use the reference,
     * You need copy it and valued a new address.
     *
     * @param linkedString second linkedString .
     * @return new Linked that concat tow LinkedString to a new LinkedString not use the reference
     */
    @Override
    public LinkedString<Character> concat(LinkedString<E> linkedString) {
        Character[] oldChars =
                this.toString().chars().mapToObj(c -> (char) c).toArray(Character[]::new);
        Character[] concatChars =
                linkedString.toString().chars().mapToObj(c -> (char) c).toArray(Character[]::new);
        Character[] bothChars = concat(oldChars, concatChars);
        LinkedString<Character> newLinkedString = new LinkedString<>(bothChars, bothChars.length);

        return newLinkedString;
    }

    public static <T> T[] concat(T[] first, T[] second) {
        T[] result = Arrays.copyOf(first, first.length + second.length);
        System.arraycopy(second, 0, result, first.length, second.length);
        return result;
    }

    /**
     * If the LinkedString is empty ,this method will return true.
     *
     * @return this method will return true if the LinkedString is empty
     */
    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public String toString() {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < size; i++) {
            stringBuilder.append(getNode(i).getItem());
        }
        return stringBuilder.toString();
    }

    /**
     * Get the length of the String (how many char(Node) in it.)
     *
     * @return length
     */
    @Override
    public int length() {
        return size;
    }

    /**
     * split the String to a small String.
     *
     * @param startIndex start index.
     * @param endIndex  end index.
     * @return return s subString.
     */
    @Override
    public LinkedString<Character> subString(int startIndex, int endIndex) {
        String substring = this.toString().substring(startIndex, endIndex);
        Character[] chars =
                substring.chars().mapToObj(c -> (char) c).toArray(Character[]::new);
        LinkedString<Character> subLinkedString = new LinkedString<>(chars, chars.length);
        return subLinkedString;
    }


    /**
     * Init this String with 1 char.
     *
     * @param charValue charValue.
     */
    public LinkedString(E charValue) {
        this.first = new Node<>(charValue, null, null);
        this.last = first;
        this.size = 1;
    }

    /**
     * Init this linkedString by chars[]
     *
     * @param chars chars[]
     */

    public LinkedString(E[] chars, int length) {
        LinkedString<E> linkedString = null;
        if (chars != null || length == 0) {
            linkedString = new LinkedString<>(chars[0]);
            for (int i = 1; i < length; i++) {
                linkedString.insertLast(chars[i]);
            }
        } else {
            System.err.println("the array chars[] param is null!");
            throw new NullPointerException();
        }
        this.first = linkedString.first;
        this.last = linkedString.last;
        this.size = linkedString.size;
    }

    /**
     * Init this LinkedString by String.
     *
     * @param string String.
     */
    public LinkedString(String string) {
        //char to Character.
        Character[] chars =
                string.chars().mapToObj(c -> (char) c).toArray(Character[]::new);
        LinkedString linkedString = new LinkedString<>(chars, chars.length);
        this.size = linkedString.size;
        this.first = linkedString.first;
        this.last = linkedString.last;
    }

    /**
     * insert a Node to at the index.
     *
     * @param index index
     * @param data  data
     */
    public void insert(int index, E data) {
        Node<E> node = new Node<>(data, null, null);
        if (isEmpty()) {
            first = node;
            last = node;
            size++;
        } else {
            //if the index out the the size.we just add it in the tail of the LinkedString.
            if (index >= size) {
                Node<E> tempNode = last;
                last = node;
                //set the previous node of last.
                last.setPrevious(tempNode);
                //set next
                tempNode.setNext(last);
                size++;
            } else if (index == 0) {
                Node<E> oldFirst = first;
                first = node;
                oldFirst.setPrevious(first);
                first.setNext(oldFirst);
                size++;
            }
            // if index in the range of size
            else {
                Node<E> oldIndexNode = getNode(index);
                Node<E> previousNode = getNode(index - 1);
                previousNode.setNext(node);
                node.setNext(oldIndexNode);
                node.setPrevious(previousNode);
                oldIndexNode.setPrevious(node);
                size++;
            }
        }

    }

    /**
     * Insert Last index.
     *
     * @param data
     */
    public void insertLast(E data) {
        int index = size;
        insert(index, data);
    }

    public void insertFirst(E data) {
        insert(0, data);
    }

    /**
     * Return the node that in the index.
     *
     * @param index index.
     */
    public Node<E> getNode(int index) {
        if (index == 0) {
            return first;
        } else if (index == (size - 1)) {
            return last;
        } else if (index > 0 && index < (size - 1)) {
            Node<E> tempNode = first;
            for (int i = 0; i < size; i++) {
                if (i == index) {
                    return tempNode;
                } else {
                    tempNode = tempNode.getNext();
                }
            }
        }
        System.err.println("the index=" + index + " and it out of range(0," + size + ")");
        throw new IndexOutOfBoundsException();
    }


}
